package fons.navigator.views.piemap;

import java.awt.Color;
import java.awt.Font;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.batik.ext.awt.RadialGradientPaint;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolox.util.PFixedWidthStroke;
import fons.model.ontology.Context;
import fons.model.ontology.Dimension;
import fons.model.ontology.Filters;
import fons.model.ontology.Value;
import fons.model.ontology.requirement.Requirement;
import fons.model.ontology.requirement.Sum;
import fons.navigator.util.box2d.DynamicPlacer;
import fons.navigator.util.box2d.DynamicallyPlaceable;
import fons.navigator.util.piccolo.PLabel;
import fons.navigator.util.piccolo.PNodes;


public class PieChartDrawingStrategy extends DrawingStrategy implements DynamicallyPlaceable {
	private static final long serialVersionUID = 1L;
	private static final Stroke SLICE_STROKE = null;// new PFixedWidthStroke(1);

	private static final Font LABEL_FONT = new Font("Arial", Font.PLAIN, 20);
	private static final Color ANCHOR_STROKE_PAINT = new Color(150, 150, 150, 80);
	private static final Color LEGEND_ANCHOR_STROKE_PAINT = new Color(150, 150, 150, 200);
	private static final Stroke ANCHOR_STROKE = new PFixedWidthStroke(2);
	private static final PFixedWidthStroke STROKE = new PFixedWidthStroke(3);

	private final PieChart pieChart;
	private final PText nodeName;
	private final PNode pieNode;
	private final PieChartLegend legend;
	
	private Anchor nodeAnchor;
	
	private class PieChartLegend extends PNode {
		private static final long serialVersionUID = 1L;
		private final List<PLabel> legends;
		private final List<PPath> anchors;

		public PieChartLegend (boolean show) {
			this.legends = new ArrayList<PLabel>(pieChart.size());
			this.anchors = new ArrayList<PPath>(pieChart.size());

			initialize();
			setVisible(show);
		}

		private void initialize () {
			final double radius = pieChart.getRadius();
			final int size = pieChart.size();

			final StringBuilder labelBuilder = new StringBuilder();
			double height = 25;

			if (2*radius/size >= height)
				height = 2*radius/size;

			int counter = 0;
			int reverseCounter = 0;

			for (PieChart.PieSlice slice: pieChart) {
				double textRadius = 2*radius;
				if (textRadius > 200) textRadius = 200;
				else if (textRadius < 100) textRadius = 100;

				if (slice.getLabel() != null) {
					labelBuilder.append(counter+reverseCounter+1);
					labelBuilder.append(".: ");
					labelBuilder.append(((int)(10000*slice.getValue()/pieChart.getTotal()+0.5)/100.0));
					labelBuilder.append("% ");
					labelBuilder.append(slice.getLabel());

				} else {
					labelBuilder.append("Nichts");
				}
				final PLabel eachLegend = new PLabel(labelBuilder.toString());        			
				labelBuilder.delete(0, labelBuilder.length());

				double y;
				if (slice.getMidAngle() < 220) {
					y = counter++ * height;
				} else {
					y = size*height - (++reverseCounter)*height;
				}


				eachLegend.translate(eachLegend.getTextNode().getHeight(), y);
				eachLegend.setPickable(true);

				this.addChild(eachLegend);
				legends.add(eachLegend);

				PPath rect = new PPath();
				rect.setPathTo(new Rectangle2D.Double(0, 0, eachLegend.getTextNode().getHeight(), eachLegend.getTextNode().getHeight()));
				rect.setPaint(slice.getColor());
				rect.translate(-eachLegend.getTextNode().getHeight(), 0);
				eachLegend.addChild(rect);
				rect.moveToBack();
			}

			PieChartDrawingStrategy.this.addChild(this);
			this.translate(radius * 1.5, -this.getFullBoundsReference().getHeight()/2.0);

			Iterator<PieChart.PieSlice> sliceIter = pieChart.iterator();
			for (PLabel eachLegend: legends) {
				PieChart.PieSlice slice = sliceIter.next();

				Anchor anchor = Anchor.fromTo(eachLegend, PieChartDrawingStrategy.this, slice);

				this.addChild(anchor);
				anchors.add(anchor);
				anchor.moveInBackOf(eachLegend);

				anchor.setStroke(STROKE);
				anchor.setStrokePaint(LEGEND_ANCHOR_STROKE_PAINT);
			}
		}
	}

	public PieChartDrawingStrategy(Target target, PNode positionHint) {
		super(target, positionHint);

		this.pieNode = new PNode();
		this.pieChart = PieChart.of(target, target.getTargetSpace(),
				getRadius(target, target.getPieDimension()),
				target.getPieMap().getVisualCanvas().getView().getViewConfig());

		this.nodeName = new PText(target.getTargetDimension().getAbbreviation());
		this.nodeName.setFont(LABEL_FONT);
		this.nodeName.setPaint(Color.WHITE);
		

		this.drawPie(getPositionHint());
		this.legend = new PieChartLegend(target.getPieMap().getShowLegends() && target.isSelected());
		this.moveToBack();
	}


	private final static double minR = 20.0;
	private final static double maxR = 120.0;

	public static double getMaxRadius() {
		return maxR * PieMap.getPieSize();
	}

	public static double getMinRadius() {
		return minR * PieMap.getPieSize();
	}

	private double getRadius(final Target vnode, final Dimension pieDimension) {
		Value minValue = vnode.getPieMap().getMin(pieDimension, vnode.getPieMap().getTargetDimensions());
		Value maxValue = vnode.getPieMap().getMax(pieDimension, vnode.getPieMap().getTargetDimensions());	

		if (minValue == Context.missingValue || maxValue == Context.missingValue) {
			@SuppressWarnings("unchecked")
			Requirement requirement = new Sum()
			.of(pieDimension.children())
			.in(getTarget().getSubParts())
			.as(pieDimension)
			.each(Lists.<Set<Dimension>>newArrayList(
					getTarget().getPieMap().getTargetDimensions(),
					Sets.<Dimension>newHashSet(getTarget().getPieMap().getValueDimensions()),
					getTarget().getSubParts()));

			getTarget().getContext().provide(requirement);

			minValue = vnode.getPieMap().getMin(pieDimension, vnode.getPieMap().getTargetDimensions());
			maxValue = vnode.getPieMap().getMax(pieDimension, vnode.getPieMap().getTargetDimensions());        	
		} 

		double min = minValue.getValue();
		double max = maxValue.getValue();

		if (min == max) {
			if (min < 0)
				max = Math.abs(min);
			else
				max = 2*min;
		}

		double size = 0;

		for (Value value: vnode.getTargetSpace().filter(Filters.anyOf(vnode.getPartDimensions())
				.and(Filters.anyOf(vnode.getSubParts())))) {
			if (value != Context.missingValue && value != null)
				size += value.getValue();

		}

		double tmp = (size - min) / (max - min);
		return tmp * (getMaxRadius() - getMinRadius()) + getMinRadius();
	}

	private final void drawPie (PNode positionHint) {
		this.reset();

		if (positionHint != null) {
			Point2D point = positionHint.getBoundsReference().getCenter2D();
			positionHint.localToGlobal(point);
			PNodes.moveTo(this, point.getX(), point.getY());
			if (this.nodeAnchor != null) {
				this.removeChild(this.nodeAnchor);
			}

			this.nodeAnchor = Anchor.fromParentTo(this, this.parentToLocal(getTarget().getBounds().getCenter2D()));
			this.nodeAnchor.setStroke(ANCHOR_STROKE);
			this.nodeAnchor.setStrokePaint(ANCHOR_STROKE_PAINT);

		} else {
			this.translate(getTarget().getX(), getTarget().getY());
		}

		final double radius = pieChart.getRadius();

		int arcType = Arc2D.PIE;

		if (pieChart.size() <= 1) {
			arcType = Arc2D.OPEN;
		}

		for (Iterator<PieChart.PieSlice> iter = pieChart.iterator(); iter.hasNext();) {
			final PieChart.PieSlice slice = iter.next();
			final double arcAngle = iter.hasNext() ? slice.getArcAngle() : 360 - slice.getStartAngle();

			Arc2D arc = new Arc2D.Double(-radius, -radius,
					radius*2, radius*2,
					slice.getStartAngle(),
					arcAngle,
					arcType);

			final PPath pslice = new PPath();
			pslice.append(arc, true);
			pieNode.addChild(pslice);

			if (slice.hasGradient()) {
				float[] dists = new float[] { 0.70f, 0.71f };
				Color[] colors = new Color[] { slice.getGradientColor(), slice.getColor() };

				final RadialGradientPaint paint = new RadialGradientPaint(new Point2D.Double(0,0), (float)radius,
						dists, colors);
				pslice.setPaint(paint);

			} else {
				pslice.setPaint(slice.getColor());
			}
			pslice.setStroke(SLICE_STROKE);

		}
		this.addChild(pieNode);
		this.addChild(nodeName);
		nodeName.translate(-nodeName.getWidth()/2, radius+nodeName.getHeight()/2);

		if (this.nodeAnchor != null)
			this.nodeAnchor.moveToBack();
	}


	@Override
	public final void showLegend () {
		legend.setVisible(true);
	}

	@Override
	public final void hideLegend () {
		legend.setVisible(false);
	}

	@Override
	public Point2D getGlobalOrigin() {
		Point2D p = getTarget().getPoint();
		p = getTarget().getPieMap().getCamera().viewToLocal(new Point2D.Double(p.getX(), p.getY()));
		this.setGlobalTranslation(p);

		if (this.legend.getVisible()) {
			return this.localToGlobal(new Point2D.Double(
					0.5* legend.getFullBoundsReference().getWidth(),
					0));

		} else {
			return this.localToGlobal(new Point2D.Double(0, 0));
		}
	}

	@Override
	public double getShapeRadius() {
		return this.pieChart.getRadius() * getTarget().getPieMap().getPieDistance();
	}

	@Override
	public void placeAt(Point2D globalPoint) {
		if (this.legend.getVisible()) {
			globalPoint.setLocation(
					globalPoint.getX() - 0.5* legend.getFullBoundsReference().getWidth(),
					globalPoint.getY());
		}

		this.globalToLocal(globalPoint);
		PNodes.moveTo(this, globalPoint.getX(), globalPoint.getY());

		if (this.nodeAnchor != null) {
			this.removeChild(this.nodeAnchor);
		}
		this.nodeAnchor = Anchor.fromParentTo(this, this.getParent().getBounds().getCenter2D());
		this.nodeAnchor.setStroke(ANCHOR_STROKE);
		this.nodeAnchor.setStrokePaint(ANCHOR_STROKE_PAINT);
	}

	@Override
	public void setPlacer (DynamicPlacer placer) {
		placer.add(this);
	}

	@Override
	public void removePlacer(DynamicPlacer placer) {
		placer.remove(this);
	}

	@Override
	public ShapeType getShapeType() {
		return this.legend.getVisible() ? ShapeType.RECTANGLE : ShapeType.CIRCLE;
	}

	@Override
	public double getShapeHeight() {
		final double pieDistance = getTarget().getPieMap().getPieDistance();
		return 0.5* Math.max((0.5 + pieDistance) * pieChart.getRadius(), (pieDistance - 0.5) * legend.getFullBoundsReference().getHeight());
	}

	@Override
	public double getShapeWidth() {
		final double pieDistance = getTarget().getPieMap().getPieDistance();
		return 0.5* ((1.0 + pieDistance) * pieChart.getRadius() + (pieDistance - 0.5) * legend.getFullBoundsReference().getWidth());
	}

	@Override
	public double getAnchorRadius() {
		return 2.5;
	}

	@Override
	public double getMass() {
		return 0.1;
	}

	@Override
	public void zOrder() {
		getTarget().moveToFront();
	}
}
